// 下载代码 感谢其实雨很好（https://gitee.com/qsyhh） 的分享
// 感谢 https://chat.deepseek.com/ 、 https://grok.com/ 的帮助
//
import { promisify } from 'util';
import { pipeline } from 'stream';
import plugin from '../../lib/plugins/plugin.js';
import fs from 'node:fs';
import path from 'node:path';
import { exec } from 'node:child_process';
import schedule from 'node-schedule';
import yaml from 'js-yaml';

const _path = process.cwd();
const streamPipeline = promisify(pipeline);
const CONFIG_DIR = path.join(`${_path}/plugins/mora-plugin/config/user`);
const RESOURCE_DIR = path.join(`${_path}/plugins/mora-plugin/Strategyresources/user`);
const CONFIG_FILES = {
  genshin: path.join(`${CONFIG_DIR}/roleGuides.yaml`),
  starrail: path.join(`${CONFIG_DIR}/srRoleGuides.yaml`),
  zzz: path.join(`${CONFIG_DIR}/zzzRoleGuides.yaml`)
};
const RESOURCE_FILES = {
  genshin: path.join(`${RESOURCE_DIR}/roleGuides.yaml`),
  starrail: path.join(`${RESOURCE_DIR}/srRoleGuides.yaml`),
  zzz: path.join(`${RESOURCE_DIR}/zzzRoleGuides.yaml`)
};
// 定时任务时间配置（cron定时表达式）
/*  按照顺序填写时间字段，空格分隔
秒（0-59）

分钟（0-59）

小时（0-23）

日期（1-31）

月份（1-12 或 JAN-DEC）

星期几（1-7 或 SUN-SAT，其中1代表星期日）

*/
const SCHEDULE_CONFIG = {
  UPDATE_REPOSITORY: '0 0 4,13 * * *',        // 每天 4点/13点 更新仓库
  UPDATE_CONFIG: '0 0 3 * * 1',               // 每周一 03:00:00 更新配置文件
  UPDATE_MATERIAL: '0 40 13 * * 3',           // 每周三 13:40:00 下载材料
  UPDATE_PLUGIN: '0 0 2 * * 1',               // 每周一 02:00:00 更新插件
  CHECK_CHANGES: '0 0 0 * * *'                // 每天 00:00:00 检查文件变化
};

export class MoraImagePlugin extends plugin {
  constructor() {
    super({
      name: '摩拉攻略材料下载器',
      desc: '根据 JSON 文件下载并分类保存攻略和材料图片',
      event: 'message',
      priority: 50,
      rule: [
        { reg: '^#下载?(\\S+)?(攻略|材料)(\\s*\\d+|\s*全部)?$', fnc: 'handleDownloadRequest' },
        { reg: '^#强制下载?(\\S+)?(攻略|材料)(\\s*\\d+|\s*全部)?$', fnc: 'handleForceDownloadRequest' },
        { reg: '^#(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源列表$', fnc: 'promptStrategySources', permission: 'master' },
        { reg: '^#设置(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源\\s*(\\d+(?:\\s+\\d+)*)?$', fnc: 'setStrategySources', permission: 'master' },
        { reg: '^#查看(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源$', fnc: 'viewStrategySources' },
        { reg: '^#更新配置文件$', fnc: 'handleUpdateConfig', permission: 'master' },
        { reg: '^#下载器js更新$', fnc: 'handleUpdatejs', permission: 'master' },
        { reg: '^#更新仓库$', fnc: 'handleUpdateRepository' },
        { reg: '^#摩拉攻略帮助$', fnc: 'handleHelp' }
      ],
    });

    this.initScheduledTasks();

    this.baseDir = path.join(`${_path}/plugins/mora-plugin/data/roleGuides`);
    this.materialDir = path.join(`${_path}/plugins/Atlas/star-rail-atlas/material for role`);
    this.guideDir = path.join(`${_path}/plugins/Atlas/star-rail-atlas/guide for role`);
    this.strategyResourcesDir = path.join(`${_path}/plugins/mora-plugin/Strategyresources`);
    this.strategyDir = path.join(this.strategyResourcesDir, 'strategy');
    this.materialDirSrc = path.join(this.strategyResourcesDir, 'material');
    this.strategyBackupDir = path.join(this.strategyResourcesDir, 'strategy_1');
    this.materialBackupDir = path.join(this.strategyResourcesDir, 'material_1');

    this.strategyList = [
      { name: '猫冬', url: `${this.strategyDir}/猫冬.json` },
      { name: 'OH是姜姜呀', url: `${this.strategyDir}/OH是姜姜呀.json` },
      { name: 'HoYo青枫', url: `${this.strategyDir}/HoYo青枫.json` },
      { name: 'Asgater', url: `${this.strategyDir}/Asgater.json` },
      { name: '小橙子阿', url: `${this.strategyDir}/小橙子阿.json` },
      { name: '紫喵Azunya', url: `${this.strategyDir}/紫喵Azunya.json` },
      { name: '洛羽Lox', url: `${this.strategyDir}/洛羽Lox.json` },
      { name: '让我摸个鱼吧Moyu', url: `${this.strategyDir}/让我摸个鱼吧Moyu.json` },
      { name: '地底Tv', url: `${this.strategyDir}/地底Tv.json` },
      { name: '拓荒姬Offical', url: `${this.strategyDir}/拓荒姬Offical.json` },
      { name: '紫罗兰打烊啦', url: `${this.strategyDir}/紫罗兰打烊啦.json` },
      { name: '阿巴辣', url: `${this.strategyDir}/阿巴辣.json` },
      { name: '星穹中心', url: `${this.strategyDir}/星穹中心.json` },
      { name: '祈鸢ya', url: `${this.strategyDir}/祈鸢ya.json` }
    ];

    this.materialList = [
      { name: '让我摸个鱼吧Moyu', url: `${this.materialDirSrc}/让我摸个鱼吧Moyu.json` },
      { name: '小橙子阿', url: `${this.materialDirSrc}/小橙子阿.json` }
    ];

    this.strategySources = {
      genshin: this.loadStrategySources('genshin'),
      starrail: this.loadStrategySources('starrail'),
      zzz: this.loadStrategySources('zzz')
    };

    this.pendingGame = null;
    this.pendingSources = null;
    this.timeoutId = null;

    this.linkCache = new Map();
  }

  loadLocalJson(filePath) {
    try {
      if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
      return [];
    } catch (error) {
      logger.mark(`[攻略材料下载器] 加载 JSON 失败: ${filePath} - ${error.message}`);
      return [];
    }
  }

  loadStrategySources(game) {
    try {
      const file = CONFIG_FILES[game];
      if (fs.existsSync(file)) {
        const data = yaml.load(fs.readFileSync(file, 'utf-8'));
        let sources = [];
        if (game === 'genshin' && data && typeof data === 'object' && Array.isArray(data.news)) {
          sources = data.news;
        } else if (Array.isArray(data)) {
          sources = data;
        }
        if (sources.length > 0) {
          return sources.map(s => this.strategyList.findIndex(item => item.name === s.source));
        }
        return [];
      }
      return [];
    } catch (error) {
      logger.mark(`[攻略材料下载器] 加载${game}攻略源配置失败: ${error.message}`);
      return [];
    }
  }

  loadResourceSources(game) {
    try {
      const file = RESOURCE_FILES[game];
      if (fs.existsSync(file)) {
        const data = yaml.load(fs.readFileSync(file, 'utf-8'));
        if (game === 'genshin' && data && typeof data === 'object' && Array.isArray(data.news)) {
          return data.news;
        } else if (Array.isArray(data)) {
          return data;
        }
        logger.mark(`[攻略材料下载器] 警告: ${game} 的资源 YAML 文件没有有效的攻略源数组，返回空数组`);
        return [];
      }
      return [];
    } catch (error) {
      logger.mark(`[攻略材料下载器] 加载${game}资源攻略源失败: ${error.message}`);
      return [];
    }
  }

  saveStrategySources(game, selectedSources, allSources) {
    fs.mkdirSync(CONFIG_DIR, { recursive: true });
    const yamlData = game === 'genshin' ? {
      olds: null,
      news: selectedSources.map(i => allSources[i])
    } : selectedSources.map(i => allSources[i]);
    fs.writeFileSync(CONFIG_FILES[game], yaml.dump(yamlData, { skipInvalid: true }));
  }

  async promptStrategySources(e) {
    const gameMatch = e.msg.match(/^#(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源列表$/);
    if (!gameMatch) return;

    const gameInput = gameMatch[1];
    const game =
      ['原神', '原', 'gs', 'gi'].includes(gameInput) ? 'genshin' :
        ['星铁', '铁', 'sr', 'hsr'].includes(gameInput) ? 'starrail' : 'zzz';
    const gameName = game === 'genshin' ? '原神' : game === 'starrail' ? '星铁' : '绝区零';

    const allSources = this.loadResourceSources(game);
    if (allSources.length === 0) {
      await e.reply(`未找到${gameName}的攻略源，请检查资源文件。`);
      return;
    }

    const sourceList = allSources.map((s, i) => `${i + 1}. ${s.source}`).join('\n');
    await e.reply(`可用的${gameName}攻略源：\n${sourceList}\n请直接回复 #设置${gameName}攻略源 后接序号，数字之间用空格分开，如：#设置${gameName}攻略源 1 2 3\n不接序号则清空攻略源`);
  }

  async setStrategySources(e) {
    const match = e.msg.match(/^#设置(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源\s*(\d+(?:\s+\d+)*)?$/);
    if (!match) return;

    const gameInput = match[1];
    const game =
      ['原神', '原', 'gs', 'gi'].includes(gameInput) ? 'genshin' :
        ['星铁', '铁', 'sr', 'hsr'].includes(gameInput) ? 'starrail' : 'zzz';
    const gameName = game === 'genshin' ? '原神' : game === 'starrail' ? '星铁' : '绝区零';

    const allSources = this.loadResourceSources(game);
    if (allSources.length === 0) {
      await e.reply(`未找到${gameName}的攻略源，请检查资源文件。`);
      return;
    }

    const indicesStr = match[2] || '';
    const indices = indicesStr ? indicesStr.split(/\s+/).map(num => parseInt(num) - 1) : [];

    if (indices.length > 0 && indices.some(i => i < 0 || i >= allSources.length)) {
      await e.reply('序号无效，请检查后重试。');
      return;
    }

    this.strategySources[game] = indices;
    this.saveStrategySources(game, indices, allSources);
    await e.reply(indices.length > 0
      ? `已设置${gameName}攻略源：${indices.map(i => allSources[i].source).join(', ')}`
      : `已清空${gameName}攻略源`);
  }

  async viewStrategySources(e) {
    const gameMatch = e.msg.match(/#查看(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源/);
    const gameInput = gameMatch[1];
    const game =
      ['原神', '原', 'gs', 'gi'].includes(gameInput) ? 'genshin' :
        ['星铁', '铁', 'sr', 'hsr'].includes(gameInput) ? 'starrail' : 'zzz';
    const gameName = game === 'genshin' ? '原神' : game === 'starrail' ? '星铁' : '绝区零';
    const file = CONFIG_FILES[game];

    if (!fs.existsSync(file)) {
      await e.reply(`未设置${gameName}攻略源。`);
      return;
    }

    const data = yaml.load(fs.readFileSync(file, 'utf-8'));
    let sources = [];
    if (game === 'genshin' && data && typeof data === 'object' && Array.isArray(data.news)) {
      sources = data.news;
    } else if (Array.isArray(data)) {
      sources = data;
    }

    if (sources.length === 0) {
      await e.reply(`未设置${gameName}攻略源。`);
    } else {
      const sourceList = sources.map((s, i) => `${i + 1}. ${s.source}`).join('\n');
      await e.reply(`当前${gameName}攻略源：\n${sourceList}`);
    }
  }

  async handleDownloadRequest(e) {
    const match = e.msg.match(/^#下载?(\S+)?(攻略|材料)(\s*\d+|\s*全部)?$/);
    if (!match) {
      await e.reply('命令格式错误！示例：#下载那刻夏攻略1 或 #下载攻略全部');
      return;
    }
    const role = match[1] || null; // 角色名，可能为 null
    const type = match[2]; // "攻略" 或 "材料"
    const target = match[3]?.trim() || '全部'; // 序号或 "全部"
    await this.processDownload(e, false, role, target, type);
  }

  async handleForceDownloadRequest(e) {
    const match = e.msg.match(/^#强制下载?(\S+)?(攻略|材料)(\s*\d+|\s*全部)?$/);
    if (!match) {
      await e.reply('命令格式错误！示例：#强制下载那刻夏攻略1 或 #强制下载攻略全部');
      return;
    }
    const role = match[1] || null; // 角色名，可能为 null
    const type = match[2]; // "攻略" 或 "材料"
    const target = match[3]?.trim() || '全部'; // 序号或 "全部"
    await this.processDownload(e, true, role, target, type);
  }

  async processDownload(e, forceUpdate, role, target, type) {
    const baseDir = type === '攻略' ? this.baseDir : (fs.existsSync(this.materialDir) ? this.materialDir : this.guideDir);
    const game = type === '攻略' ? 'starrail' : null;
    let totalSuccessCount = 0;

    if (type === '攻略') {
      const sources = this.strategySources[game];
      if (!sources || sources.length === 0) {
        await e.reply('请先设置星铁攻略源。');
        return;
      }

      if (!role) {
        if (target === '全部') {
          await e.reply(forceUpdate ? `开始强制下载所有攻略源的攻略，请稍候...` : `开始下载所有攻略源的攻略，请稍候...`);
          for (const i of sources) {
            const item = this.strategyList[i];
            totalSuccessCount += await this.downloadData(item.url, forceUpdate, baseDir, null, type);
          }
          await e.reply(forceUpdate ? `强制下载所有攻略完成！成功下载 ${totalSuccessCount} 张图片。` : `下载所有攻略完成！成功下载 ${totalSuccessCount} 张图片。`);
        } else {
          const index = parseInt(target) - 1;
          if (index >= 0 && index < sources.length) {
            const item = this.strategyList[sources[index]];
            await e.reply(forceUpdate ? `开始强制下载 ${item.name} 的攻略，请稍候...` : `开始下载 ${item.name} 的攻略，请稍候...`);
            totalSuccessCount = await this.downloadData(item.url, forceUpdate, baseDir, null, type);
            await e.reply(forceUpdate ? `强制下载 ${item.name} 的攻略完成！成功下载 ${totalSuccessCount} 张图片。` : `下载 ${item.name} 的攻略完成！成功下载 ${totalSuccessCount} 张图片。`);
          } else {
            await e.reply('序号无效，请检查后重试。');
            return;
          }
        }
      } else {
        await e.reply(forceUpdate ? `开始强制下载${role}${type}，请稍候...` : `开始下载${role}${type}，请稍候...`);
        if (target === '全部') {
          for (const i of sources) {
            const item = this.strategyList[i];
            totalSuccessCount += await this.downloadData(item.url, forceUpdate, baseDir, role, type);
          }
        } else {
          const index = parseInt(target) - 1;
          if (index >= 0 && index < sources.length) {
            const item = this.strategyList[sources[index]];
            totalSuccessCount = await this.downloadData(item.url, forceUpdate, baseDir, role, type);
          } else {
            await e.reply('序号无效，请检查后重试。');
            return;
          }
        }
        await e.reply(forceUpdate ? `强制下载${role}${type}完成！成功下载 ${totalSuccessCount} 张图片。` : `下载${role}${type}完成！成功下载 ${totalSuccessCount} 张图片。`);
      }
    } else {
      await e.reply(forceUpdate ? `开始强制下载${role || '所有'}${type}，请稍候...` : `开始下载${role || '所有'}${type}，请稍候...`);
      if (target === '全部') {
        for (const item of this.materialList) {
          totalSuccessCount += await this.downloadData(item.url, forceUpdate, baseDir, role, type);
        }
      } else {
        const index = parseInt(target) - 1;
        if (index >= 0 && index < this.materialList.length) {
          const item = this.materialList[index];
          totalSuccessCount = await this.downloadData(item.url, forceUpdate, baseDir, role, type);
        } else {
          await e.reply('序号无效，请检查后重试。');
          return;
        }
      }
      await e.reply(forceUpdate ? `强制下载${role || '所有'}${type}完成！成功下载 ${totalSuccessCount} 张图片。` : `下载${role || '所有'}${type}完成！成功下载 ${totalSuccessCount} 张图片。`);
    }
  }

  async downloadData(url, forceUpdate, baseDir, role, type, changedLinks = null) {
    const data = this.loadLocalJson(url);
    if (!data || (Array.isArray(data) && data.length === 0) || (typeof data === 'object' && Object.keys(data).length === 0)) return 0;

    if (!changedLinks) {
      const contentHash = JSON.stringify(data);
      const cachedHash = this.linkCache.get(url);
      if (cachedHash && cachedHash !== contentHash) {
        logger.mark(`[攻略材料下载器] 检测到 ${url} 内容变动，将强制下载`);
        forceUpdate = true;
      }
      this.linkCache.set(url, contentHash);
    }

    return await this.processJson(data, forceUpdate, baseDir, role, type, changedLinks);
  }

  async processJson(data, forceUpdate, baseDir, role, type, changedLinks = null) {
    let successCount = 0;

    if (!data || (Array.isArray(data) && data.length === 0) || (typeof data === 'object' && Object.keys(data).length === 0)) {
      logger.mark(`[攻略材料下载器] 数据为空或无效: ${JSON.stringify(data)}`);
      return 0;
    }

    if (type === '攻略') {
      if (typeof data !== 'object' || Array.isArray(data)) {
        logger.mark(`[攻略材料下载器] 攻略数据格式错误，期望对象，实际为: ${typeof data}`);
        return 0;
      }

      for (const [author, folders] of Object.entries(data)) {
        const authorDir = path.join(baseDir, author);
        if (!fs.existsSync(authorDir)) fs.mkdirSync(authorDir, { recursive: true });

        if (typeof folders !== 'object' || Array.isArray(folders)) {
          logger.mark(`[攻略材料下载器] 攻略文件夹数据格式错误: ${author}`);
          continue;
        }

        for (const [folder, entries] of Object.entries(folders)) {
          const folderDir = path.join(authorDir, folder);
          if (!fs.existsSync(folderDir)) fs.mkdirSync(folderDir, { recursive: true });

          if (!Array.isArray(entries)) {
            logger.mark(`[攻略材料下载器] 攻略条目格式错误: ${author}/${folder}`);
            continue;
          }

          for (const entry of entries) {
            if (!entry.name || !entry.link) continue;
            if (!role || entry.name === role) {
              const fileName = `${entry.name}.jpg`;
              try {
                const downloaded = await this.downloadImage(entry.link, folderDir, fileName, forceUpdate);
                if (downloaded) successCount++;
              } catch (error) {
                logger.mark(`[攻略材料下载器] 下载失败: ${entry.link} - ${error.message}`);
              }
            }
          }
        }
      }
    } else {
      if (!Array.isArray(data)) {
        logger.mark(`[攻略材料下载器] 材料数据格式错误，期望数组，实际为: ${typeof data}`);
        return 0;
      }

      const saveDir = path.join(baseDir);
      if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true });

      for (const entry of data) {
        if (!entry.name || !entry.link) continue;
        if (!role || entry.name === role) {
          const fileName = `${entry.name}.png`;
          try {
            const downloaded = await this.downloadImage(entry.link, saveDir, fileName, forceUpdate);
            if (downloaded) successCount++;
          } catch (error) {
            logger.mark(`[攻略材料下载器] 下载失败: ${entry.link} - ${error.message}`);
          }
        }
      }
    }
    return successCount;
  }

  async downloadImage(url, saveDir, fileName, forceUpdate) {
    const filePath = path.join(saveDir, fileName);
    if (!forceUpdate && fs.existsSync(filePath)) return false;

    const response = await fetch(url);
    if (!response.ok) throw new Error(`下载失败: ${response.statusText}`);
    await streamPipeline(response.body, fs.createWriteStream(filePath));
    return true;
  }

  async handleUpdateConfig(e) {
    await e.reply('开始更新配置文件，请稍候...');
    const commands = [
      'curl -o "./plugins/mora-plugin/model/roleGuide.js" "https://gitee.com/mingdiandianzhu/data/raw/master/local/roleGuide.js"'
    ];

    for (const command of commands) await this.executeCommand(command);
    await e.reply('配置文件更新成功！');
  }

  async handleUpdatejs(e) {
    await e.reply('开始更新本插件，请稍候...');
    const command = 'curl -o "./plugins/example/LocalStrategyMaterialDownloader.js" "https://gitee.com/mingdiandianzhu/data/raw/master/local/LocalStrategyMaterialDownloader.js"';
    await this.executeCommand(command);
    await e.reply('本插件更新成功！');
  }

  async handleUpdateRepository(e) {
    await e.reply('开始检查并更新仓库，请稍候...');

    if (!fs.existsSync(this.strategyResourcesDir)) {
      const moraPluginDir = path.join(`${_path}/plugins/mora-plugin`);
      if (!fs.existsSync(moraPluginDir)) fs.mkdirSync(moraPluginDir, { recursive: true });
      const command = `git clone https://gitcode.com/qq_72727809/Strategyresources.git ${this.strategyResourcesDir}`;
      await this.executeCommand(command);
      await e.reply('仓库下载完成！');
    } else {
      const pullCommand = `cd ${this.strategyResourcesDir} && git pull`;
      try {
        await this.executeCommand(pullCommand);
        await e.reply('仓库更新完成！');
      } catch (error) {
        if (error.message.includes('would be overwritten by merge')) {
          logger.mark(`[攻略材料下载器] 检测到合并冲突，将删除仓库并重新下载`);
          await e.reply('检测到本地文件冲突，正在删除仓库并重新下载...');

          try {
            fs.rmSync(this.strategyResourcesDir, { recursive: true, force: true });
            const moraPluginDir = path.join(`${_path}/plugins/mora-plugin`);
            if (!fs.existsSync(moraPluginDir)) fs.mkdirSync(moraPluginDir, { recursive: true });
            const cloneCommand = `git clone https://gitcode.com/qq_72727809/Strategyresources.git ${this.strategyResourcesDir}`;
            await this.executeCommand(cloneCommand);
            await e.reply('仓库重新下载完成！');
          } catch (cloneError) {
            logger.mark(`[攻略材料下载器] 重新下载仓库失败: ${cloneError.message}`);
            await e.reply(`重新下载仓库失败，请手动处理：${cloneError.message}`);
          }
        } else {
          logger.mark(`[攻略材料下载器] 更新仓库失败: ${error.message}`);
          await e.reply(`更新仓库失败：${error.message}`);
        }
      }
    }
  }

  executeCommand(command) {
    return new Promise((resolve, reject) => {
      exec(command, (error, stdout, stderr) => {
        if (error) {
          reject(new Error(stderr || error.message));
        } else {
          resolve(stdout);
        }
      });
    });
  }

  copyDirectory(src, dest) {
    if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
    const files = fs.readdirSync(src);
    for (const file of files) {
      const srcPath = path.join(src, file);
      const destPath = path.join(dest, file);
      if (fs.statSync(srcPath).isDirectory()) {
        this.copyDirectory(srcPath, destPath);
      } else {
        fs.copyFileSync(srcPath, destPath);
      }
    }
  }

  compareLinks(dir1, dir2) {
    const changedLinks = [];
    if (!fs.existsSync(dir2)) return changedLinks;

    const files1 = fs.readdirSync(dir1);
    const files2 = fs.readdirSync(dir2);

    for (const file of files1) {
      const path1 = path.join(dir1, file);
      const path2 = path.join(dir2, file);
      if (fs.existsSync(path2)) {
        const data1 = this.loadLocalJson(path1);
        const data2 = this.loadLocalJson(path2);

        if (Array.isArray(data1)) {
          const map2 = new Map(data2.map(item => [item.name, item.link]));
          for (const item of data1) {
            const oldLink = map2.get(item.name);
            if (oldLink && oldLink !== item.link) {
              changedLinks.push({ file: path1, link: item.link, name: item.name });
            }
          }
        } else {
          for (const [author, folders] of Object.entries(data1)) {
            if (data2[author]) {
              for (const [folder, entries] of Object.entries(folders)) {
                if (data2[author][folder]) {
                  const map2 = new Map(data2[author][folder].map(item => [item.name, item.link]));
                  for (const entry of entries) {
                    const oldLink = map2.get(entry.name);
                    if (oldLink && oldLink !== entry.link) {
                      changedLinks.push({ file: path1, link: entry.link, name: entry.name });
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return changedLinks;
  }

  async initScheduledTasks() {
    const mockEvent = { reply: (msg) => logger.mark(`[攻略材料下载器] ${msg}`), log: (msg) => logger.mark(`[攻略材料下载器] ${msg}`) };

    schedule.scheduleJob(SCHEDULE_CONFIG.UPDATE_REPOSITORY, async () => {
      mockEvent.log('开始执行仓库更新 (每天 4点/13点)');
      await this.handleUpdateRepository(mockEvent);
      mockEvent.log('仓库更新完成 (每天 4点/13点)');
    });

    schedule.scheduleJob(SCHEDULE_CONFIG.UPDATE_CONFIG, async () => {
      mockEvent.log('开始执行配置文件更新 (每周一 03:00:00)');
      await this.handleUpdateConfig(mockEvent);
      mockEvent.log('配置文件更新完成 (每周一 03:00:00)');
    });

    schedule.scheduleJob(SCHEDULE_CONFIG.UPDATE_MATERIAL, async () => {
      mockEvent.log('开始执行材料下载 (每周三 13:40:00)');
      await this.processDownload(mockEvent, false, null, '全部', '材料');
      mockEvent.log('材料下载完成 (每周三 13:40:00)');
    });

    schedule.scheduleJob(SCHEDULE_CONFIG.UPDATE_PLUGIN, async () => {
      mockEvent.log('开始执行插件更新 (每周一 02:00:00)');
      await this.handleUpdatejs(mockEvent);
      mockEvent.log('插件更新完成 (每周一 02:00:00)');
    });

    schedule.scheduleJob(SCHEDULE_CONFIG.CHECK_CHANGES, async () => {
      mockEvent.log('开始检查文件变化并备份 (每天 00:00:00)');
      const changedStrategyLinks = this.compareLinks(this.strategyDir, this.strategyBackupDir);
      const changedMaterialLinks = this.compareLinks(this.materialDirSrc, this.materialBackupDir);

      if (changedStrategyLinks.length > 0) {
        mockEvent.log(`检测到 strategy 文件夹链接变化，变化链接数: ${changedStrategyLinks.length}`);
        for (const { file, link, name } of changedStrategyLinks) {
          const strategyName = path.basename(file, '.json');
          const strategyItem = this.strategyList.find(item => item.name === strategyName);
          if (strategyItem && this.strategySources.starrail.some(i => this.strategyList[i].name === strategyName)) {
            mockEvent.log(`强制下载变化链接: ${strategyName} ${name} (${link})`);
            const data = this.loadLocalJson(file);
            await this.processJson(data, true, this.baseDir, name, '攻略', [link]);
          }
        }
      }

      if (changedMaterialLinks.length > 0) {
        mockEvent.log(`检测到 material 文件夹链接变化，变化链接数: ${changedMaterialLinks.length}`);
        for (const { file, link, name } of changedMaterialLinks) {
          const materialName = path.basename(file, '.json');
          const materialItem = this.materialList.find(item => item.name === materialName);
          if (materialItem) {
            mockEvent.log(`强制下载变化链接: ${materialName} ${name} (${link})`);
            const data = this.loadLocalJson(file);
            await this.processJson(data, true, this.materialDir || this.guideDir, name, '材料', [link]);
          }
        }
      }

      this.copyDirectory(this.strategyDir, this.strategyBackupDir);
      this.copyDirectory(this.materialDirSrc, this.materialBackupDir);
      mockEvent.log('文件备份完成 (每天 00:00:00)');
    });

    logger.mark(`[攻略材料下载器] 定时任务已初始化`);
  }

  async handleHelp(e) {
    const helpText = `摩拉攻略材料下载器帮助：
  【顺序：更新仓库,xx攻略源列表,设置xx攻略源 序号,更新配置文件，下载攻略/材料】

  1. #下载<角色名>(攻略|材料)[序号|全部] 示例 #下载那刻夏攻略4 或 #下载那刻夏攻略全部
  2. #强制下载<角色名>(攻略|材料)[序号|全部] 示例 #强制下载那刻夏攻略4 或 #强制下载那刻夏攻略全部
  3. #(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源列表 示例 #原神攻略源列表
  4. #设置(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源 [序号] 示例 #设置sr攻略源 1 2 3
  5. #查看(原神|原|gs|gi|星铁|铁|sr|hsr|绝区零|绝|zzz)攻略源 示例 #查看sr攻略源
  6. #更新配置文件
  7. #下载器js更新
  8. #更新仓库 (若本地文件冲突，将删除仓库并重新下载)
  9. #摩拉攻略帮助

  攻略目录：${this.baseDir}
  材料目录：${this.materialDir} 或 ${this.guideDir}

  攻略作者列表：
  ${this.strategyList.map((s, i) => `${i + 1}. ${s.name}`).join('\n')}
  材料作者列表：
  ${this.materialList.map((m, i) => `${i + 1}. ${m.name}`).join('\n')}`;
    await e.reply(helpText);
  }
}